fix(palette): restore composer remount for never-activated pet widgets - #4605
fix(palette): restore composer remount for never-activated pet widgets#4605Yeachan-Heo wants to merge 5 commits into
Conversation
which only admits the current overlay owner or a widget whose epoch still matches. A widget that never activated (pet.mode "off" at startup) claims neither, so InteractiveMode.restoreComposer() -- which always routes through petWidget.remountComposer() once init() creates the widget -- silently no-opped. Palette cancel/close paths then left their CommandPaletteComponent mounted in editorContainer, leaking the modal (issue #4604, three command-palette-interactive-host failures). Composer-mount authority and overlay ownership are separate concerns: a never-activated widget still owns its host's composer mount and must remount the plain editor exactly like the no-pet fallback, while only disposal or a live successor widget revokes the mount. The active-owner case now also remounts the framed editor instead of relying on the epoch clause alone. Lore-id: 4604-palette-pet-remount Constraint: must not weaken #4591 successor-takeover isolation Tested: bun test command-palette-interactive-host.test.ts (8/8) Tested: bun test gajae-pet-widget.test.ts (62 pass incl. 2 new regressions) Tested: bun test packages/tui/test/gajae-pet.test.ts (19 pass) Not-tested: live iTerm2 pet drag interaction (requires real terminal) Confidence: high Scope-risk: narrow Reversibility: trivial
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Root cause
Pre-#4591, Validation (exact PR head c194e79)
CI on this head (run 31932247745 + 31932247752)Green: affected-path plan, native-build, Red: — |
c194e79 to
b5929c8
Compare
|
Status: rebase complete, exact-head CI driven, human-review boundary reached.
No code or behavior delta vs dev; this PR only adds the regression coverage that would have caught #4591's leak before it shipped. — |
|
Blocked solely on human review — full state for the reviewer
— |
snowykr
left a comment
There was a problem hiding this comment.
Verdict: Request changes
gajae.pr-review-verdict.v1 request-changes sha256:1465ccdfd915bdfdc6b32a6431efa134faeecd6346a209b4ab49cd99aee45faf reviewer:human reviewer-id:snowykr evidence:https://github.com/Yeachan-Heo/gajae-code/actions/runs/32044402879
Reviewed the exact PR head b5929c80eca5074998e3875c0b13a7a67076d9d1 against dev 8711c555da across intent/contract, architecture/correctness, security/trust, verification/CI, and compatibility/platform context.
The regression-test direction is correct, and I found no actionable P1, P3, or P5 issue. The two new assertions cover the ownerless/off → plain-editor and active-owner → framed-editor remount branches, while the existing successor-takeover test preserves the stale-owner contract.
Required changes
1. Make the remount tests model the reusable editor lifecycle
Location: packages/coding-agent/test/gajae-pet-widget.test.ts:650-701
makeStubs() supplies an editor cast to CustomEditor, but the stub has no dispose() implementation. The production #mountEditor() uses editorContainer.clear(), and Container.clear() disposes every current child. CustomEditor.dispose() is not a no-op: it tears down the tab-width listener and resets pending paste state. The new tests therefore prove only child replacement; they can pass even if a repeated palette close/remount path has disposed the reusable editor.
Please either use a real/disposable editor in this regression or add a focused lifecycle assertion that repeats the restore path and verifies the editor remains usable (including the relevant disposal/listener contract). If the intended ownership model is detach-then-readd, the implementation/test should make that explicit rather than relying on a stub that omits disposal.
2. Refresh the exact-head evidence in the PR contract
The PR body’s verdict still points to https://github.com/Yeachan-Heo/gajae-code/actions/runs/32044088151, but that is the older/cancelled run and gh pr checks 4605 still reports its affected-path job as failed. The successful exact-head product run is 32044402879; its targeted gajae-pet-widget test, ts-build, native-build, affected-path plan/evidence, and integration jobs pass, while the contract jobs fail because the body still declares needs-human.
Please update the verdict evidence to the immutable successful exact-head run (or explicitly label the old URL historical), then rerun the exact-head contract gate after the review decision is recorded.
Review lane summary
- P1 Intent / Policy / Contract: clear.
- P2 Architecture / Correctness / Failure: changes requested for lifecycle/test fidelity; active-pet
InteractiveModeintegration remains a useful follow-up gap. - P3 Security / Privacy / Trust: not materially applicable; test-only change with no external or security boundary.
- P4 Verification / Tests / CI: targeted changed-test CI is green, but neighboring host/TUI suites are local-only claims and the body evidence URL is stale.
- P5 Context / Compatibility / Platform: clear; no generated artifacts, runtime/package changes, or changelog required for this test-only diff.
|
Exact-head Owned blockers:
The lane will implement, push, rerun exact-head CI, obtain a fresh approval/verdict, and drive the PR to dev merge. This is not a human-only hold. — |
…store Container.clear() disposes children terminally, and Editor.dispose() tears down the tab-width change listener. Opening a selector cleared editorContainer with the live composer attached, so the first palette round-trip silently killed the editor's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Overlay open paths now detach the reusable editor first (Container.detachChild, the tui detach-then-readd reuse contract), and the pet-aware restores (#mountEditor, restoreComposer, queued-message restore) detach both reusable mounts before clearing. Successor ownership isolation in remountComposer is unchanged. Regression coverage: a real-CustomEditor lifecycle test in the widget suite (disposal observably stops tab-width invalidations) and an end-to-end host test that fails on the pre-fix open path and passes with the fix (verified red/green by reverting the source change). Lore-id: pr4605-composer-lifecycle Constraint: preserve #4591 successor-takeover isolation (remountComposer untouched) Tested: red/green on command-palette-interactive-host (8+1 pass with fix; new test fails without) Tested: 264 pass across 12 adjacent suites; gajae-pet-widget 63 pass Tested: tui + coding-agent check clean; coding-agent build clean Confidence: high Scope-risk: moderate Reversibility: trivial
|
Requested changes addressed — head Both required changes from review 4952815267 are implemented, tested red/green, and pushed to the authorized branch (lease-guarded force push from 1. Remount tests now model the reusable editor lifecycle. The stub editor was replaced with a real
The review also exposed a real production defect, now fixed. 2. Verdict evidence refreshed to the immutable exact-head run. Exact-head Dev CI run 32058210086: 17 code jobs green — native-build, Validation on Follow-up gap (separate issue): share/gist loader, MCP wizard, and tasks-pane paths still use the raw @snowykr — ready for a fresh review of — |
|
Terminal disposition: blocked solely on fresh human review of
Lane holds here; no speculative merge. — |
a890974 to
cce1441
Compare
|
Terminal disposition: blocked solely on fresh human review of
No mutations while review is absent: no self-approval, no verdict change, no source/body edits. Lane returns to external review hold. — |
The framed-remount regression test constructs a real editor to exercise container disposal semantics, but does not otherwise need the local binding. Removing it keeps the affected-package check warning-free. Lore-id: pr4605-lint-cleanup Tested: bun test packages/coding-agent/test/gajae-pet-widget.test.ts (63 pass) Tested: bun run --cwd=packages/coding-agent check Confidence: high Scope-risk: trivial Reversibility: trivial
|
@snowykr Fresh non-author review requested for exact head The prior Current exact-head CI is running. The only expected governance blocker after product CI is a fresh independent approval and matching [repo owner's gaebal-gajae (clawdbot) 🦞] |
097c3b4 to
eef82ef
Compare
|
Reconstruction evidence — PR rebased onto current dev, fresh exact-head review requested
Review request: @snowykr — fresh authorized non-author review of exact head Signed: owner lane for PR #4605, head — |
|
Owner-lane boundary review complete — head The owner lane has finished its adversarial boundary review of exact head
Advisory findings (not merge blockers, recorded for follow-up): the same terminal-disposal defect remains reachable at four pre-existing unguarded open paths — jobs overlay ( @snowykr — the review request from the reconstruction comment still stands: fresh non-author review of exact head — |
snowykr
left a comment
There was a problem hiding this comment.
Verdict: Request changes
gajae.pr-review-verdict.v1 request-changes sha256:59e7bd2abe687245725790324531d00da64e9f77f6e83f31ad47bb6e07680713 reviewer:human reviewer-id:snowykr evidence:https://github.com/Yeachan-Heo/gajae-code/actions/runs/32099895353
Reviewed the new exact head eef82ef5dc5a620f847312057d3ea1eab7bbe55d against dev 27afb732b3d25632d44176687d5bdd78d3419bb3 across the requested P1–P5 axes, including the detach-before-clear production changes and the new lifecycle tests.
The production direction is sound: detaching the reusable editor before Container.clear() preserves the editor's terminally-owned lifecycle, and the exact-head affected-path jobs for the changed suites are green. I found one blocking verification/compatibility issue before approval.
Required change
Update all SelectorController test contexts for the new detachChild requirement
Locations:
packages/coding-agent/test/custom-model-preset-creation.test.ts:854-858packages/coding-agent/test/model-selector-profiles-redteam.test.ts:127-127,356-356packages/coding-agent/test/provider-onboarding-wizard.test.ts:263-269packages/coding-agent/test/provider-onboarding-wizard-redteam.test.ts:320-327
These contexts instantiate SelectorController and exercise selector-opening paths such as showModelSelector() / showCustomProviderWizard(), which all enter SelectorController.showSelector(). The new implementation now unconditionally calls this.ctx.editorContainer.detachChild(this.ctx.editor) before clear(), but these test doubles still provide only clear() and addChild(). Because they are cast to InteractiveModeContext/never, TypeScript does not catch the missing method; the affected tests will fail at runtime with detachChild is not a function when the selector opens.
Please update every lightweight editorContainer double that reaches SelectorController.showSelector() to implement detachChild with the same child-list semantics as the existing updated fixtures. A repository-wide audit should include the session-selector and login-selector test contexts as well, rather than updating only the changed-path suites.
Verification notes
- P1 Intent / Contract: clear; the detach-then-readd change matches the stated lifecycle contract.
- P2 Architecture / Correctness: no production blocker found in the reviewed call sites;
PetFramedEditoris not disposable, and the reusableCustomEditorremains owned byInteractiveMode. - P3 Security / Privacy / Trust: not materially applicable; no new external or sensitive-data boundary.
- P4 Verification / CI: the new lifecycle tests are meaningful, but current CI targets only the changed suites and therefore does not catch the unupdated selector test doubles above.
- P5 Compatibility / Platform: runtime API usage is consistent with
Container; test fixtures need to be brought to the same contract. The changelog entry is appropriate for the user-visible lifecycle fix.
The current exact-head product run is 32099895353; its relevant affected-path jobs pass. The contract jobs remain red by design while the PR body records needs-human.
snowykr
left a comment
There was a problem hiding this comment.
Verdict: Request changes
gajae.pr-review-verdict.v1 request-changes sha256:59e7bd2abe687245725790324531d00da64e9f77f6e83f31ad47bb6e07680713 reviewer:human reviewer-id:snowykr evidence:https://github.com/Yeachan-Heo/gajae-code/actions/runs/32099895353
Follow-up review of the same exact head eef82ef5dc5a620f847312057d3ea1eab7bbe55d found two additional blocking gaps in the fix-forward. The detach lifecycle direction is correct, but the queued-message path and the test/evidence contract are not complete yet.
Required changes
1. Restore the pet-aware composer from the queued-message selector
Location: packages/coding-agent/src/modes/controllers/input-controller.ts:1249-1255
#restoreEditorFocus() now detaches the raw editor and re-adds that raw editor directly. In an active pet session, the mounted child is PetFramedEditor, not ctx.editor; the detach is therefore a no-op, clear() removes the framed wrapper, and the close path mounts the bare editor. The pet remains active and continues emitting its sprite, but the composer reserve/frame is gone after closing or deleting/moving through the queued-message selector.
This is deterministic for the active-pet queue flow and is exactly the kind of reusable-composer contract this PR is fixing. Route the restore through ctx.restoreComposer() (or an equivalent pet-aware mount helper) so the active mode restores PetFramedEditor and off mode restores the plain editor. Add an active-pet queued-selector regression that asserts the framed child and reserve survive open/close.
2. Update every selector test double that now needs detachChild
The first follow-up only updated the changed-path fixtures. Several existing contexts still instantiate SelectorController and enter showSelector(), but provide only clear() and addChild(). Since showSelector() now calls detachChild() unconditionally, these tests can fail at runtime despite being cast through InteractiveModeContext/never:
packages/coding-agent/test/custom-model-preset-creation.test.ts:854-858packages/coding-agent/test/model-selector-profiles-redteam.test.ts:127,356packages/coding-agent/test/provider-onboarding-wizard.test.ts:263-269packages/coding-agent/test/provider-onboarding-wizard-redteam.test.ts:320-327packages/coding-agent/test/modes/components/theme-selector-input.test.tsselector contextspackages/coding-agent/test/modes/components/thinking-selector.test.tsselector contextspackages/coding-agent/test/selector-controller-resume-model.test.ts:11-15packages/coding-agent/test/selector-controller-session-delete.test.ts:11-15 and 49-57
Please audit all SelectorController fixtures that reach showSelector() and add a child-list-correct detachChild implementation, not just the affected-path files. The current targeted CI does not execute all of these suites.
3. Refresh the exact-head evidence references
The PR body still identifies 32099837032 / 32099835720 as the current exact-head evidence. gh reports 32099837032 cancelled and 32099835720 failed. The completed current-head product run is 32099895353; its product/affected jobs pass, while 32099895625 is the separate contract run that fails only because the body still records needs-human.
Update the body’s exact-evidence section and verdict URL to the completed run, and describe the contract failure as the expected human-review gate. Do not leave cancelled/failed runs presented as current evidence.
Additional verification note
The new real-editor widget lifecycle tests at packages/coding-agent/test/gajae-pet-widget.test.ts:671-702 and the preceding active/off tests should explicitly dispose the CustomEditor fixture after each test. widget.dispose() does not own the editor, and CustomEditor registers a process-wide tab-width listener; the first two real-editor tests can leak that listener into later tests. The repeated lifecycle test’s red control disposes it intentionally, but the other real-editor fixtures need equivalent cleanup.
The existing raw editorContainer.clear() swaps outside SelectorController.showSelector() (OAuth code input, jobs/tasks panes, command-controller loaders, and runtime-MCP wizard) also deserve a lifecycle audit before claiming the reusable composer contract is complete; I am treating those as follow-up scope unless they are intended to be covered by this PR’s broad selector/overlay fix.
Lane summary
- P1 Intent / Contract: changes requested for queued pet-aware restore and incomplete fixture contract.
- P2 Architecture / Correctness: active-pet queued selector loses
PetFramedEditordeterministically. - P3 Security / Privacy / Trust: no actionable finding; not materially applicable.
- P4 Verification / CI: lifecycle coverage is improved, but unupdated fixtures and stale evidence remain.
- P5 Compatibility / Platform: runtime
Containerusage is consistent; all lightweight contexts must match the new method contract.
…ctor Review 4958165282 on the exact head found the queued-message close path still swapped in the raw editor: in an active pet session the mounted composer child is PetFramedEditor, so #restoreEditorFocus() detached the plain editor (a no-op), let clear() drop the framed wrapper, and mounted the bare editor — the pet kept emitting while the composer lost its reserve. Route the restore through ctx.restoreComposer() (the same pet-aware helper SelectorController.showSelector's done() already prefers), falling back to the plain editor swap for contexts that predate it. Also bring every remaining SelectorController test double to the new detachChild contract (child-list-correct, not a no-op cast) and dispose the real CustomEditor fixtures in the pet-widget lifecycle tests so their process-wide tab-width listeners cannot leak into later tests. Lore-id: pr4605-review-4958165282 Constraint: preserve contributor authorship and one-PR scope Tested: bun test input-controller-keybindings (65), gajae-pet-widget, command-palette-interactive-host, selector-controller-command-palette, custom-model-preset-creation, model-selector-profiles-redteam, provider-onboarding-wizard(+redteam), theme-selector-input, thinking-selector, selector-controller-resume-model, selector-controller-session-delete, login-preset-recommendation, model-selector-controller-batch, model-selector-profiles (all green) Tested: bun run --cwd=packages/coding-agent check Confidence: high Scope-risk: moderate Reversibility: trivial
eef82ef to
11f772a
Compare
11f772a to
6d12e76
Compare
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
|
Addressed in full — fixture audit complete (review 4958123531) Every New exact head for re-review: — Fixed in Deterministic regressions added in
All 65 tests in that suite green locally. — Fixture audit. Every test context that instantiates
Verified non-fixtures: Fixture disposal. The three real- — Evidence. The PR body's exact-evidence section now names head Adjacent raw — |
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
|
Fix-forward complete on head
Remaining blocker (human-only): a fresh non-author exact-head review of — |
snowykr
left a comment
There was a problem hiding this comment.
Verdict
Request changes. The reviewed commit is 6d12e769e65f174a4615b87da9aea5d9b1b1318d. Two evidence-grounded P1 issues remain: one leaves equivalent composer lifecycle defects in production paths, and one leaves the changed queued-message path without a regression test that exercises the relevant disposal contract.
Summary
The detach-before-clear repair is correctly applied in the reviewed selector, queued-message, and pet-remount paths. However, other production flows still clear a container with the reusable composer attached, and the queued-message tests use doubles that cannot detect the Container.clear() disposal behavior being fixed.
Findings / Required Changes
- [P1] Repair the remaining composer replacement paths before clearing.
packages/coding-agent/src/modes/controllers/command-controller.ts:191-199still clearseditorContainerwhile the reusable editor is attached in/share, then re-adds that disposed instance; the default Gist branch repeats the pattern at lines 248-256. The same lifecycle defect is reported in the MCP-add wizard, OAuth prompt, Jobs overlay, and Tasks pane. BecauseContainer.clear()disposes attached children, these paths can remove the editor's tab-width listener and clear its paste state. Detach the reusable editor (or its framed owner) before clearing in each affected transition, and add focused regression coverage. - [P1] Cover the queued-message selector with real disposal semantics.
packages/coding-agent/test/input-controller-keybindings.test.ts:198-207uses a fake editor and aclear()mock that only truncates an array. Those tests would still pass if the newdetachChild(editor)immediately beforeInputController'sclear()were removed, so they do not prove that the changed path avoidsContainer.clear()→Editor.dispose(). Add an InputController-level regression using a realContainerandCustomEditor; open and restore the queued-message selector, then verify the restored composer remains usable and responds to a tab-width change.
CI / Verification
No PR code, tests, linting, formatting, or local verification commands were run by the reviewers. GitHub-observed CI on the reviewed head showed the requested changed-test checks succeeding, including command-palette controller, resume-model controller, session-delete controller, command-palette interactive host, input-controller keybindings, and the relevant pet-widget suite; the overall Dev CI run was marked failed because PR contract bootstrap failed while its product checks succeeded.
Axis Coverage
- A1 — Intent/architecture: Approved the scoped detach-before-clear lifecycle repair and changelog placement; no finding.
- A2 — Correctness: Approved the changed selector, queued-message restoration, pet ownership, and error-path behavior; no finding.
- A3 — Security: Found no actionable security, privacy, or trust issue.
- A4 — Verification: Raised the queued-message real-disposal regression coverage gap.
- A5 — Compatibility/completeness: Raised the remaining equivalent production composer-replacement lifecycle defects.
d9af002 to
6e56702
Compare
|
Base containment fixed — new exact head
@snowykr — your two reviews (4958123531, 4958165282) are fully addressed; the head has only been rebased since, with an unchanged source delta. Fresh exact-head review of — |
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
|
Exact-head CI green on
@snowykr — every finding from your reviews 4958123531 and 4958165282 is implemented and verified on this exact head (pet-aware queued-message restore, repository-wide fixture — |
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
… paths Review 4964398363 on the exact head found two P1 gaps: 1. Equivalent composer-replacement defects remained in production paths: /share custom-export and default-gist loaders, the OAuth API-key code input, the jobs overlay, the tasks pane, the MCP-add wizard, and the /debug log/raw-SSE viewers all cleared editorContainer with the live composer attached, so Container.clear() disposed the editor and its tab-width listener. Each open path now detaches the reusable composer first; close-path clears were already safe once their opens detach. This closes the follow-up scope filed in #4657. 2. The queued-message regressions used an array-double editorContainer, which cannot detect Container.clear() disposal. Added an InputController-level regression over a real Container + CustomEditor: three open/restore cycles through the queue selector with tab-width toggles proving the restored composer keeps its listener and input state (red-control verified: removing the open-path detach fails it). Also gave the /share temp-security double child-list-correct detachChild semantics. Lore-id: pr4605-review-4964398363 Tested: bun test input-controller-keybindings (66 incl. new real-disposal regression), share-command-temp-security, debug/log-viewer, and the full 15-suite focused surface (264 pass) Tested: bun run --cwd=packages/coding-agent check Confidence: high Scope-risk: moderate Reversibility: trivial
|
Both P1 findings of review 4964398363 implemented — new head
[P1] Remaining composer replacement paths — all repaired in
A repository-wide sweep confirms every remaining [P1] Real disposal semantics for the queued-message selector — added Validation on @snowykr — both P1s of your latest review are addressed head-on in — |
|
Exact-head CI green on
@snowykr — fresh exact-head review of — |
snowykr
left a comment
There was a problem hiding this comment.
Verdict
CHANGES_REQUESTED — reviewed exact head 3196de229c3980ed8886458f52c52147cf60b991 against dev base 648d13d72d0749c068eb9278e04f2a6ba0b07b78.
Summary
The raw-editor detach-before-clear() direction addresses the stated terminal-disposal defect. However, pet-enabled overlay flows still detach and restore the raw editor while the mounted child is PetFramedEditor. That leaves the actual child attached when Container.clear() runs, so it is disposed and later reused. The regression coverage also does not exercise this controller-level pet path.
Findings / Required Changes
-
[P1] Detach and restore the composition that is actually mounted in pet mode.
input-controller.ts:1358-1360detaches onlyctx.editor, even thoughinput-controller.ts:1283-1285establishes that an active pet mountsPetFramedEditor. The detach is therefore a no-op in pet mode; the following terminalclear()disposes the framed composer, and the close path reattaches a disposed child. The same raw-editor-only pattern remains in the changed selector, command, and MCP overlay paths (for exampleselector-controller.ts:1344-1346,command-controller.ts:193-195, andruntime-mcp-command-controller.ts:508-510). Route these transitions through one pet-aware detach/restore authority so the mounted composition—not just its inner editor—is preserved across everyclear(). -
[P1] Add a controller-level regression for the active-pet queued-message selector lifecycle.
The current pet-widget test clears the frame and checks object identity (gajae-pet-widget.test.ts:686-703), while its repeated real-editor disposal probe is never activated (gajae-pet-widget.test.ts:710-763). It cannot detect the queued-message controller disposing the real mounted child. Exercise active-pet queue-selector cancel, final-item delete, and move/refresh transitions, then assert that input remains usable and tab-width invalidation still reaches the composer after restoration. -
[P2] Preserve focus and rendering when replacing the custom-share overlay.
command-controller.ts:193-195removes the loader without theui.setFocus(loader)andui.requestRender()transition setup. Restore those calls (or provide the equivalent through the shared composition-aware helper) so the custom-share loader is immediately focused and rendered after the swap.
CI / Verification
- Reviewed GitHub Actions evidence for this exact head; the scoped selector suites,
gajae-pet-widget.test.ts,input-controller-keybindings.test.ts, andcheck:@gajae-code/coding-agentare reported as passing in Dev CI #32186689415. - The remaining exact-head failures are the
needs-humangovernance checks (PR contract bootstrap/Validate exact-head PR contract), which are excluded from this verdict. - This review was static/CI-artifact based; no PR code, test suite, build, linter, or formatter was executed.
Axis Coverage
| Axis | Result |
|---|---|
| A1. Intent / Policy / Contract | Block — the supported pet-mode contract is not preserved. |
| A2. Architecture / Correctness / Failure | Block — clear() can still dispose the mounted pet-framed composer. |
| A3. Security / Privacy / Trust | No actionable finding — no changed trust-boundary regression identified. |
| A4. Verification / Tests / CI | Block — the pet-mode controller disposal path lacks a detecting regression. |
| A5. Context / Compatibility / Platform | Block — restored active-pet overlays can lose their framed composer behavior. |
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
What
Fixes a live editor-lifecycle defect in selector and command-palette overlay transitions, with regression coverage that exercises disposal semantics instead of merely checking child replacement.
Container.clear()terminally disposes attached children. Before this change, selector opens and queued-message-selector opens calledclear()while the reusable composer was attached;Editor.dispose()then removed the tab-width listener. Later restores re-mounted that disposed editor, so runtime tab-width changes stopped updating composer layout after the first overlay cycle.The implementation uses the established TUI detach-then-readd contract:
52dad458's successor-ownership guard inremountComposer()unchanged;restoreComposer()so an active pet keeps itsPetFramedEditormount and composer reserve after closing/deleting/moving through the queue selector.Current-dev value and non-duplication
Current
dev(648d13d72d0749c068eb9278e04f2a6ba0b07b78) still does not detach the composer beforeSelectorController.showSelector, queued-message-selector opens, or the relevant restore clears. This PR therefore retains a distinct production fix plus end-to-end lifecycle coverage.Reconstruction (base moved dev 6696988 → 27afb73 → 2bd7b4a → ceb3134 → 08bef6c → 648d13d)
d162301d08,cce1441cbe,097c3b4a1b, theneef82ef5dcat base27afb732b3) were rebased onto fresh dev648d13d72d(via2bd7b4a48c/ceb31349c2/08bef6cf88) withgit rebase --committer-date-is-author-date, preserving contributor authorshipYeachan Heo <yeachan.heo@gmail.com>and original author dates. The intervening dev commits touch none of this PR's source files; the source delta is unchanged from the accepted review scope.3196de229c(details in the review replies).Exact evidence
3196de229c3980ed8886458f52c52147cf60b991dev648d13d72d0749c068eb9278e04f2a6ba0b07b78sha256:d7376d7fd523a31784246c3f91a23ad56e6b2843752e746bd723acce66857633fromgit diff --binary --full-index --no-ext-diff 648d13d72d...3196de229c32181321301(Dev CI on6e5670266b— every product job green; superseded only by this fix-forward commit),32154701523(6d12e769e6, all product green),32099895353(eef82ef5dc, all product green), cancelled32099837032/32181054337, and contract-red32099835720/32099895625/32181321780(the contract runs failed solely on the by-designneeds-humanverdict gate). Exact-head CI on3196de229c: Dev CI 32185025067 — every product job green, including the newshare-command-temp-securityshard and the expandedinput-controller-keybindingssuite carrying the real-disposal regression. The only red isPR contract bootstrap, which fails by design while the body recordsneeds-human.3196de229c: 264 focused tests / 0 fail across all 15 named suites (incl. the new real-Container/CustomEditor queued-message disposal regression and the/share+/debugviewer suites), plusshare-command-temp-security,debug/log-viewer,mcp-quarantine-surface,credential-auto-import-flows,interactive-mode-editor-component,resume-session-reentrancy;bun run --cwd=packages/coding-agent checkclean;git diff --checkclean.Review state
The prior
CHANGES_REQUESTEDreviews by @snowykr (4952815267 atb5929c80ec; 4958123531 and 4958165282 ateef82ef5dc) are addressed head-on in3196de229c; signed replies are posted on both exact-head reviews. Owner: @Yeachan-Heo. The sole merge blocker is a fresh non-author exact-head review and matchingmerge-approvedverdict after current-head CI is green.GJC verdict
[repo owner's gaebal-gajae (clawdbot) 🦞]